package xpn.platform.modules.attachment;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import org.apache.commons.fileupload.util.Streams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import xpn.platform.common.config.XpnPlatformConfig;
import xpn.platform.common.exception.XpnBusinessException;
import xpn.platform.common.exception.XpnExceptionCodeEnum;
import xpn.platform.common.util.Tool;
import xpn.platform.modules.sys.org.SysOrg;
import xpn.platform.modules.sys.user.SysUser;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.math.BigDecimal;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 附件服务实现
 *
 * @author bobatkm Oct 6, 2017
 */

@Service
public class AttachmentServiceImpl implements AttachmentService {
	private static final int DEFAULT_BUFFER_SIZE = 20480; // ..bytes = 20KB.
	private static final long DEFAULT_EXPIRE_TIME = 604800000L; // ..ms = 1
	// week.
	private static final String MULTIPART_BOUNDARY = "MULTIPART_BYTERANGES";
	@Autowired
	XpnPlatformConfig xpnPlatformConfig;

	@Autowired
	private AttachmentDAO attachmentDAO;

	// 上载附件
	@Override
	public Object uploadAttachment(String entityLink, String category,String categoryName, String saveFilename,
			boolean saveAttachmentInfo, boolean thumbnail, int width, int height,String uploadType,HttpServletRequest request)
			throws Throwable {

		List<String> fileList = new ArrayList<String>();
        JSONArray ret=new JSONArray();
		// 0 获得上载文件夹
		String folder = this.getAttachmentFolder(entityLink, category);

		// 1 将当前上下文初始化给 CommonsMutipartResolver （多部分解析器）
		CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(
				request.getSession().getServletContext());

		// 2 检查form中是否有enctype="multipart/form-data"
		if (!multipartResolver.isMultipart(request)) {
			Object[] args = { folder };
			throw new XpnBusinessException(XpnExceptionCodeEnum.UPLOAD_FILE_FALED, null, args);
		}

		// 3 将request变成多部分request
		MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;

		// 4 获取multiRequest 中所有的文件名
		Iterator<String> iter = multiRequest.getFileNames();

		// 5 遍历每个上载文件，保存到指定目录
		while (iter.hasNext()) {
			// 一次遍历所有文件
			MultipartFile file = multiRequest.getFile(iter.next().toString());
			if (file != null) {
				String fileName = file.getOriginalFilename();
				// 如果文件名带本地路径名，则只取文件名：郑波2017-12-4增加，解决IE浏览器上传文件的问题
				int pos = fileName.lastIndexOf("\\");
				if (pos > 0) {
					fileName = fileName.substring(pos + 1);
				}
				// ---END---

				// bobatkm 2018-4-15：可以指定
				if (!saveFilename.equals("") && multiRequest.getFileMap().size() ==1) {
					fileName = saveFilename;
				}
				// ---END---

				if (!"".equals(fileName.trim())) {
					String path = folder + "/" + fileName;

					File localFile = new File(path);
					if (localFile.exists()) {
						localFile.delete();
					}
					// 上传
					file.transferTo(new File(path));

					// 如果需要生成缩略图,则生成缩略图
					if (thumbnail) {
						thumbnailPictureFile(path, path + ".thumbnail.jpg", width, height);
					}
					fileList.add(fileName);

					// 插入记录
					if (saveAttachmentInfo) { // bobatkm 2018-4-12修改：是否保存附件信息到数据库中
						Attachment attachment = new Attachment();
						attachment.setEntityLink(entityLink);
						attachment.setFilename(fileName);
						attachment.setCategory(category);
						attachment.setCategoryName(categoryName);
						String urlparam=attachment.fetchUrlParam();
						attachment=attachmentDAO.save(attachment);
						JSONObject jo=new JSONObject();
						jo.put("filepath", path);
						jo.put("id", attachment.getId());
					    if(category!=null&&!"".equals(category)){
					    	jo.put("category", category);
					    }
						ret.add(jo);
					}
				}
			}
		}
        if(uploadType!=null&&uploadType.equals("preupload")){
        	return ret;
        }else{
    		return fileList;
        }
	}

	public boolean domoveFile(String fileName,String destinationFloderUrl)
    {
        File file = new File(fileName);
        File destFloder = new File(destinationFloderUrl);
        //检查目标路径是否合法
        if(destFloder.exists())
        {
            if(destFloder.isFile())
            {
                System.out.println("目标路径是个文件，请检查目标路径！");
                return false;
            }
        }else
        {
            if(!destFloder.mkdirs())
            {
            	System.out.println("目标文件夹不存在，创建失败！");
                return false;
            }
        }
        //检查源文件是否合法
        if(file.isFile() &&file.exists())
        {
            String destinationFile = destinationFloderUrl+"/"+file.getName();
            if(!file.renameTo(new File(destinationFile)))
            {
                return false;
            }
            File pf=file.getParentFile();
            if(pf!=null){
                if(pf.isDirectory()){
                	File pff=pf.getParentFile();
                	pf.delete();
                	if(pff!=null){
                		if(pff.isDirectory()){
                			pff.delete();
                		}
                	}

                }
            }

        }else
        {
            return false;
        }
        return true;
    }


	// 删除文件
	@Override
	public boolean deleteAttachment(String entityLink, String category, String fileName) {

		String deleteFile = this.getAttachmentFolder(entityLink, category) + "/" + fileName;

		File f = new File(deleteFile);
		if (!f.exists()) {
			return false;
		}

		f.delete();

		// 如果存在缩略图，则删除缩略图
		deleteFile += ".thumbnail.jpg";
		f = new File(deleteFile);
		if (f.exists()) {
			f.delete();
		}

		// 如果目录为空，则删除目录
		String filePath = this.getAttachmentFolder(entityLink, category);
		File folder = new File(filePath);
		if (folder.list() != null) {
			if (folder.list().length == 0) {
				folder.delete();
			}
		} else {
			folder.delete();
		}

		return true;
	}

	/*
	 * 获取附件文件夹
	 */
	@Override
	public String getAttachmentFolder(String entityLink, String category) {
		String rootPath = xpnPlatformConfig.getAttachment().getRoot();
		String folder = rootPath + entityLink;
		if (!Tool.isEmpty(category)) {
			folder += "/" + category;
		}

		File f = new File(folder);
		if (!f.exists()) {
			f.mkdirs();
		}

		return folder;
	}

	// 下载文件
	@Override
	public void downloadAttachment(HttpServletRequest request, HttpServletResponse response, String entityLink,
			String category, String fileName, boolean forceDownload, boolean imgSrc) throws Throwable {
		String dirName = this.getAttachmentFolder(entityLink, category);
		String localFile;

		// 如果不指定文件名，则返回目录下的第一个文件
		if (fileName.isEmpty()) {
			File dirFile = new File(dirName);
			// 如果dir对应的文件不存在，或者不是一个文件夹则退出
			if ((!dirFile.exists()) || (!dirFile.isDirectory())) {
				response.sendError(HttpServletResponse.SC_NOT_FOUND);
				return;
			}

			File[] files = dirFile.listFiles();
			if (files.length == 0) {
				response.sendError(HttpServletResponse.SC_NOT_FOUND);
				return;
			}
			localFile = dirName + "/" + files[0].getName();
			fileName = files[0].getName();
		} else { // 指定了文件名
			// bobatkm 2018-4-14
			if (fileName.indexOf("/") >= 0 || fileName.indexOf("\\") >= 0) { // fileName是文件全路径
				localFile = fileName;
			} else { // fileName只是文件名
				localFile = dirName + "/" + fileName;
			}
		}

		String downloadFile = localFile;

		if (imgSrc) {
			downloadFile = downloadFile + ".thumbnail.jpg";
		}

		File f = new File(downloadFile);
		if (!f.exists()) {
			if (imgSrc) {
				downloadFile = localFile;
				f = new File(downloadFile);
				if (!f.exists()) {
					response.sendError(HttpServletResponse.SC_NOT_FOUND);
					return;
				}
			} else {
				response.sendError(HttpServletResponse.SC_NOT_FOUND);
				return;
			}
		}

		response.setContentLengthLong(f.length());
		String encodedFileName = new String(fileName.getBytes("utf-8"), "iso8859-1");
		if (forceDownload) { // 强迫下载
			response.setHeader("Content-Disposition",
					"attachment;filename=" + URLEncoder.encode(encodedFileName, "iso8859-1"));
			response.setContentType("application/x-msdownload");
		} else { // 打开文件
			response.setHeader("Content-Disposition",
					"inline;filename=" + URLEncoder.encode(encodedFileName, "iso8859-1"));
			String contentType = URLConnection.guessContentTypeFromName(fileName);
			if (contentType == null || "".equals(contentType.trim())) {
				contentType = "application/octet-stream";
			}
			response.setContentType(contentType);
		}

		OutputStream out = response.getOutputStream();

		String range = request.getHeader("Range");
		if (null != range && !"".equals(range.trim())) { // 分块传输
			downloadPartialContentOfFile(request, response, f, range);
		} else { // 一次性传输
			// 1 检查请求
			if (validateRequest(request, response, f) == false) {
				return;
			}

			// 2 初始化答复
			initResponse(request, response, f, false);

			// 3 发送文件内容
			Streams.copy(new FileInputStream(downloadFile), out, true);
		}
	}

	@Override
	public Object moveFile(JSONObject json) {
		JSONObject ret=new JSONObject();
		SimpleDateFormat sdf1=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		try {
			if(json!=null){
				JSONArray array=json.getJSONArray("files");
				if(array.size()>0){
	              for(int i=0;i<array.size();i++){
	            	  JSONObject jo=array.getJSONObject(i);
	            	  Long id=jo.getLong("id");
	            	  String filepath=jo.getString("filepath");
	            	  String despath=jo.getString("despath");
	            	  Attachment at=attachmentDAO.getOne(id);
	            	  at.setEntityLink(despath);
	            	  attachmentDAO.saveAndFlush(at);
	            	  String rootpath=xpnPlatformConfig.getAttachment().getRoot();
	            	  if(jo.containsKey("category")){
	            		  despath+="/"+jo.getString("category");
	            	  }
	            	  despath=rootpath+despath;
	            	  Boolean result=domoveFile(filepath,despath);
	            	  System.out.println(result);
	              }
				}

			}
		return  ret;
		} catch (Exception e) {
			ret.put("ok", false);
			// TODO Auto-generated catch block
			e.printStackTrace();
			return ret;
		}

	}

	@Override
	public Boolean hasAttachment(String entityLink, String category, String fileName) throws Throwable {
		String dirName = this.getAttachmentFolder(entityLink, category);
		String localFile;
		localFile = dirName + "/" + fileName;
		File f = new File(localFile);
		if (!f.exists()) {
			return  false;
		}
		return true;
	}

	/*
	 * 下载文件部分内容
	 */
	public boolean downloadPartialContentOfFile(HttpServletRequest request, HttpServletResponse response, File file,
			String range) throws Throwable {
		response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
		response.setHeader("Accept-Ranges", "bytes");

		// 1 检查请求
		if (validateRequest(request, response, file) == false) {
			return false;
		}

		// 2// Validate and process Range and If-Range headers.
		long length = file.length();
		Range full = new Range(0, length - 1, length);
		List<Range> ranges = validateRange(request, response, file, full);
		if (ranges == null) {
			return false;
		}

		// 3 初始化答复
		initResponse(request, response, file, true);

		// 4 发送请求的内容到客户端
		sendRequestContent(request, response, file, ranges, full);
		return true;
	}

	/*
	 * Validate request
	 */
	public boolean validateRequest(HttpServletRequest request, HttpServletResponse response, File file)
			throws Throwable {
		String fileName = file.getName();
		long length = file.length();
		long lastModified = file.lastModified();
		String eTag = fileName + "_" + length + "_" + lastModified;
		long expires = System.currentTimeMillis() + DEFAULT_EXPIRE_TIME;

		// If-None-Match header should contain "*" or ETag. If so, then return
		// 304.
		String ifNoneMatch = request.getHeader("If-None-Match");
		if (ifNoneMatch != null && matches(ifNoneMatch, fileName)) {
			response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
			response.setHeader("ETag", eTag); // Required in 304.
			response.setDateHeader("Expires", expires); // Postpone cache with 1
			// week.
			return false;
		}

		// If-Modified-Since header should be greater than LastModified. If so,
		// then return 304.
		// This header is ignored if any If-None-Match header is specified.
		long ifModifiedSince = request.getDateHeader("If-Modified-Since");
		if (ifModifiedSince != -1 && ifModifiedSince + 1000 > lastModified) {
			response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
			response.setHeader("ETag", eTag); // Required in 304.
			response.setDateHeader("Expires", expires); // Postpone cache with 1
			// week.
			return false;
		}

		// If-Match header should contain "*" or ETag. If not, then return 412.
		String ifMatch = request.getHeader("If-Match");
		if (ifMatch != null && !matches(ifMatch, fileName)) {
			response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
			return false;
		}

		// If-Unmodified-Since header should be greater than LastModified. If
		// not, then return 412.
		long ifUnmodifiedSince = request.getDateHeader("If-Unmodified-Since");
		if (ifUnmodifiedSince != -1 && ifUnmodifiedSince + 1000 <= lastModified) {
			response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
			return false;
		}

		String range = request.getHeader("Range");
		if (range != null) {
			// Range header should match format "bytes=n-n,n-n,n-n...". If not,
			// then return 416.
			if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) {
				response.setHeader("Content-Range", "bytes */" + length); // Required
				// in
				// 416.
				response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
				return false;
			}
		}
		return true;
	}

	/*
	 * Validate and process Range and If-Range headers.
	 */
	public List<Range> validateRange(HttpServletRequest request, HttpServletResponse response, File file, Range full)
			throws Throwable {
		String fileName = file.getName();
		long length = file.length();
		long lastModified = file.lastModified();
		String eTag = fileName + "_" + length + "_" + lastModified;

		List<Range> ranges = new ArrayList<Range>();

		String ifRange = request.getHeader("If-Range");
		if (ifRange != null && !ifRange.equals(eTag)) {
			try {
				long ifRangeTime = request.getDateHeader("If-Range"); // Throws
				// IAE
				// if
				// invalid.
				if (ifRangeTime != -1 && ifRangeTime + 1000 < lastModified) {
					ranges.add(full);
				}
			} catch (IllegalArgumentException ignore) {
				ranges.add(full);
			}
		}

		// If any valid If-Range header, then process each part of byte range.
		if (ranges.isEmpty()) {
			String range = request.getHeader("Range");

			for (String part : range.substring(6).split(",")) {
				// Assuming a file with length of 100, the following examples
				// returns bytes at:
				// 50-80 (50 to 80), 40- (40 to length=100), -20 (length-20=80
				// to length=100).
				long start = sublong(part, 0, part.indexOf("-"));
				long end = sublong(part, part.indexOf("-") + 1, part.length());

				if (start == -1) {
					start = length - end;
					end = length - 1;
				} else if (end == -1 || end > length - 1) {
					end = length - 1;
				}

				// Check if Range is syntactically valid. If not, then return
				// 416.
				if (start > end) {
					response.setHeader("Content-Range", "bytes */" + length); // Required
					// in
					// 416.
					response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
					return null;
				}

				// Add range.
				ranges.add(new Range(start, end, length));
			}
		}

		return ranges;
	}

	/*
	 * Initialize response.
	 */
	public void initResponse(HttpServletRequest request, HttpServletResponse response, File file, boolean acceptRanges)
			throws Throwable {
		String fileName = file.getName();
		long length = file.length();
		long lastModified = file.lastModified();
		String eTag = fileName + "_" + length + "_" + lastModified;
		long expires = System.currentTimeMillis() + DEFAULT_EXPIRE_TIME;

		if (acceptRanges) {
			response.setHeader("Accept-Ranges", "bytes");
		}
		response.setHeader("ETag", eTag);
		response.setDateHeader("Last-Modified", lastModified);
		response.setDateHeader("Expires", expires);
	}

	/*
	 * Send requested file (part(s)) to client
	 */
	public void sendRequestContent(HttpServletRequest request, HttpServletResponse response, File file,
			List<Range> ranges, Range full) throws Throwable {

		// Prepare streams.
		RandomAccessFile input = null;
		OutputStream output = null;

		try {
			// Open streams.
			input = new RandomAccessFile(file, "r");
			output = response.getOutputStream();

			if (ranges.isEmpty() || ranges.get(0) == full) {
				// Return full file.
				Range r = full;
				response.setHeader("Content-Range", "bytes " + r.start + "-" + r.end + "/" + r.total);
				response.setHeader("Content-Length", String.valueOf(r.length));

				copy(input, output, r.start, r.length);
			} else if (ranges.size() == 1) {
				// Return single part of file.
				Range r = ranges.get(0);
				response.setHeader("Content-Range", "bytes " + r.start + "-" + r.end + "/" + r.total);
				response.setHeader("Content-Length", String.valueOf(r.length));
				response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206.

				// Copy single part range.
				copy(input, output, r.start, r.length);
			} else {
				// Return multiple parts of file.
				response.setContentType("multipart/byteranges; boundary=" + MULTIPART_BOUNDARY);
				response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206.

				// Cast back to ServletOutputStream to get the easy println
				// methods.
				ServletOutputStream sos = (ServletOutputStream) output;

				// Copy multi part range.
				for (Range r : ranges) {
					// Add multipart boundary and header fields for every range.
					sos.println();
					sos.println("--" + MULTIPART_BOUNDARY + "--");
					sos.println("Content-Range: bytes " + r.start + "-" + r.end + "/" + r.total);

					// Copy single part range of multi part range.
					copy(input, output, r.start, r.length);
				}

				// End with multipart boundary.
				sos.println();
				sos.println("--" + MULTIPART_BOUNDARY + "--");
			}
		} finally {
			close(output);
			close(input);
		}
	}

	private void close(Closeable resource) {
		if (resource != null) {
			try {
				resource.close();
			} catch (IOException ignore) {
			}
		}
	}

	private void copy(RandomAccessFile input, OutputStream output, long start, long length) throws IOException {
		byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
		int read;

		try {
			if (input.length() == length) {
				// Write full range.
				while ((read = input.read(buffer)) > 0) {
					output.write(buffer, 0, read);
				}
			} else {
				input.seek(start);
				long toRead = length;

				while ((read = input.read(buffer)) > 0) {
					if ((toRead -= read) > 0) {
						output.write(buffer, 0, read);
					} else {
						output.write(buffer, 0, (int) toRead + read);
						break;
					}
				}
			}
		} catch (IOException ignore) {
		}
	}

	private long sublong(String value, int beginIndex, int endIndex) {
		String substring = value.substring(beginIndex, endIndex);
		return (substring.length() > 0) ? Long.parseLong(substring) : -1;
	}

	private boolean matches(String matchHeader, String toMatch) {
		String[] matchValues = matchHeader.split("\\s*,\\s*");
		Arrays.sort(matchValues);
		return Arrays.binarySearch(matchValues, toMatch) > -1 || Arrays.binarySearch(matchValues, "*") > -1;
	}

	/*
	 * 生成图片文件的缩略图
	 */
	public boolean thumbnailPictureFile(String inputFile, String outputFile, int width, int height) {
		if (width < 1) {
			width = 640;
		}
		if (height < 1) {
			height = 480;
		}

		try {
			File file = new File(inputFile);
			Image image = ImageIO.read(file);
			if (image == null) {
				return false;
			}

			float imgWidth = image.getWidth(null);// 原图宽度
			float imgHeight = image.getHeight(null);// 原图高度

			float rateW = width / imgWidth;// 宽度缩略比例
			float rateH = height / imgHeight;// 高度缩略比例

			float rate = 0;
			if (rateW < rateH) {// 宽度缩略比例大于高度缩略比例，使用宽度缩略比例
				rate = rateW;
			} else {
				rate = rateH;
			}
			// 计算缩略图最终的宽度和高度
			int newWidth = new Float(imgWidth * rate).intValue();
			int newHeight = new Float(imgHeight * rate).intValue();

			BufferedImage bufferedImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
			bufferedImage.getGraphics().drawImage(image.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH), 0,
					0, null);

			ImageIO.write(bufferedImage, "jpg", new File(outputFile));

		} catch (IOException e) {
			e.printStackTrace();
			return false;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}

		return true;
	}

	// 查询附件信息
	@Override
	public Object queryAttachments(String entityLinks, String cateogrys) {
        JSONArray ret=new JSONArray();
		if(entityLinks!=null&&!entityLinks.equals("")){
			String[] entitylinkArray = entityLinks.split(",");
			List<String> entitylist=new ArrayList<String>();
			List<String> categorylist=new ArrayList<String>();
			for(String link:entitylinkArray){
				entitylist.add(link);
			}
			if(cateogrys!=null&&!"".equals(cateogrys)){
				String[] categoryArray = cateogrys.split(",");
				for(String category:categoryArray){
					categorylist.add(category);
				}
			}
			if(entitylist.size()>0){
				List<Attachment> list=new ArrayList<Attachment>();
				if(categorylist.size()>0){
					list=attachmentDAO.queryAttachmentsInfo(entitylist, categorylist);
				}else{
					list=attachmentDAO.queryAttachmentsInfo(entitylist);
				}
				Set<String> links=new HashSet<String>();
				for(Attachment at:list){
					String entityLink=at.getEntityLink();
					String param=at.fetchUrlParam();
					String physicalPath=at.fetchPhysicalPath();
					String thumbnailParam=at.fetchThumbnailUrlParam();
					JSONObject jo=null;
					JSONObject joo=new JSONObject();
					joo.put("id", at.getId());
					joo.put("entityLink", entityLink);
					joo.put("filename", at.getFilename());
					joo.put("category", at.getCategory());
					joo.put("physicalPath",physicalPath);
					if(at.getCategoryName()!=null&&!at.getCategoryName().equals("")){
						joo.put("categoryName", at.getCategoryName());
					}else{
						joo.put("categoryName", at.getCategory());
					}
					joo.put("param", param);
					joo.put("thumbnailParam", thumbnailParam);
					if(!links.contains(entityLink)){
						links.add(entityLink);
						jo=new JSONObject();
						jo.put("entityLink", entityLink);
						List<JSONObject> attachments=new ArrayList<JSONObject>();
						attachments.add(joo);
						jo.put("attachments", attachments);
						ret.add(jo);
					}else{
						for(Object json:ret){
							JSONObject js=(JSONObject)json;
							if(js.getString("entityLink").equals(entityLink)){
								jo=js;
								break;
							}
						}
						List<JSONObject> attachments=jo.getJSONArray("attachments");
						attachments.add(joo);
					}

				}

			}
		}
		return ret;
	}



	protected class Range {
		long start;
		long end;
		long length;
		long total;
		public Range(long start, long end, long total) {
			this.start = start;
			this.end = end;
			this.length = end - start + 1;
			this.total = total;
		}
	}


}
